package org.zoomdev.zoom.common.utils;

import org.zoomdev.zoom.aop.Getter;
import org.zoomdev.zoom.aop.factory.GetterFactory;
import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.exceptions.ZoomException;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class BeanUtils {
    private static ConcurrentHashMap<Class, GetterField[]> setterMap = new ConcurrentHashMap<>();

    public static <T> T mergeMap(T dest, Map<String, Object> map) {
        return mergeMap(dest, map, true);
    }

    /**
     * 将map合并到data里面
     *
     * @param dest
     * @param map
     * @param <T>
     * @return
     */
    public static <T> T mergeMap(T dest, Map<String, Object> map, boolean ignoreNull) {
        if (map == null || dest == null) throw new IllegalArgumentException("map或者dest不能为空");

        Field[] fields = CachedClasses.getFields(dest.getClass());
        try {
            if (ignoreNull) {
                for (Field field : fields) {
                    Object value = map.get(field.getName());
                    if (value == null) {
                        continue;
                    }
                    field.set(dest, Caster.toType(value, field.getGenericType()));
                }
            } else {
                for (Field field : fields) {
                    if (map.containsKey(field.getName())) {
                        Object value = map.get(field.getName());
                        if (value == null) {
                            field.set(dest, null);
                        } else {
                            field.set(dest, Caster.toType(value, field.getGenericType()));
                        }
                    }
                }
            }
            return dest;
        } catch (Exception e) {
            throw new ZoomException(e);
        }

    }

    public static void copyField(Object dest, Object src, String fieldName) {
        if (dest == null || src == null) {
            throw new ZoomException("dest 和 src 必须都不为空");
        }
        Field srcField = Classes.getField(src.getClass(), fieldName);
        srcField.setAccessible(true);
        Field destField = Classes.getField(dest.getClass(), fieldName);
        destField.setAccessible(true);
        Classes.set(destField, dest, Classes.get(srcField, src));
    }

    public static Map toMap(Object src, Map dest) {
        if (dest == null || src == null) {
            throw new ZoomException("dest 和 src 必须都不为空");
        }
        GetterField[] g = setterMap.computeIfAbsent(src.getClass(), k -> {
            Field[] fields = CachedClasses.getFields(k);
            GetterField[] getters = new GetterField[fields.length];
            for (int i = 0, c = fields.length; i < c; ++i) {
                getters[i] = new GetterField();
                getters[i].getter = GetterFactory.FACTORY.create(fields[i]);
                getters[i].name = fields[i].getName();
            }
            return getters;
        });

        for (int i = 0, c = g.length; i < c; ++i) {
            Object value = g[i].getter.get(src);
            dest.put(g[i].name, value);
        }

        return dest;
    }

    public static Map toMap(Object src) {
        return toMap(src, true);
    }

    public static Map toMap(Object src, boolean ignoreNull) {
        if (src == null) {
            throw new ZoomException(" src 必须不为空");
        }
        Field[] fields = CachedClasses.getFields(src.getClass());
        try {
            Map result = new LinkedHashMap();
            for (Field field : fields) {
                Object value = field.get(src);
                if (ignoreNull && value == null) {
                    continue;
                }
                result.put(field.getName(), value);
            }
            return result;
        } catch (Exception e) {
            throw new ZoomException(e);
        }
    }

    public static <T> T merge(T dest, T src) {
        return merge(dest, src, true);
    }

    /**
     * 将data合并到dest,并返回dest
     * 需要注意的是，并的依据为从data中获取的字段值不为空，
     * 所以尽量不要有int等字段
     * 本方法只是浅层拷贝
     *
     * @param dest
     * @param src
     * @param <T>
     * @return
     */
    public static <T> T merge(T dest, T src, boolean ignoreNull) {
        if (dest == null || src == null) {
            throw new ZoomException("dest 和 src 必须都不为空");
        }
        Field[] fields = CachedClasses.getFields(dest.getClass());
        try {
            for (Field field : fields) {
                Object value = field.get(src);
                if (ignoreNull && value == null) {
                    continue;
                }
                field.set(dest, value);
            }
        } catch (Exception e) {
            throw new ZoomException(e);
        }
        return dest;
    }

    public static <T, E> T copyFrom(T dest, E src) {
        return copyFrom(dest, src, true);
    }

    /**
     * 不同的bean 复制数据到相同字段，需要实现保证字段类型的可转化型
     *
     * @param dest
     * @param src
     * @param <T>
     * @param <E>
     * @return
     */
    public static <T, E> T copyFrom(T dest, E src, boolean ignoreNull) {
        if (dest == null || src == null) {
            throw new ZoomException("dest 和 src 必须都不为空");
        }
        Map<String, Object> map = new HashMap<>();
        toMap(src, map);
        mergeMap(dest, map, ignoreNull);
        return dest;
    }

    /**
     * 合并list,如果data比较多，会增加到末尾
     *
     * @param dest
     * @param data
     * @param keys 主键
     * @param <T>
     * @return
     */
    public static <T> List<T> mergeList(
            List<T> dest,
            List<T> data,
            String... keys
    ) {
        if (keys.length == 0) {
            throw new ZoomException("必须提供至少一个key");
        }

        Map<String, T> destMap = CollectionUtils.toMap(
                dest, keys
        );

        Map<String, T> srcMap = CollectionUtils.toMap(
                data, keys
        );

        //merge src to dest

        Iterator<Map.Entry<String, T>> iterator = srcMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, T> entry = iterator.next();
            T d = destMap.get(entry.getKey());
            if (d != null) {
                //merge
                merge(d, entry.getValue());
                iterator.remove();
            }
        }

        dest.addAll(srcMap.values());

        return dest;
    }

    public static <T> T toBean(Object data, Class<T> classOfT) {
        return Caster.to(data, classOfT);
    }

    public static <T> T mapToBean(Map<String, Object> map, Class<T> classOfT) {
        return Caster.to(map, classOfT);
    }

    static class GetterField {
        Getter getter;
        String name;
    }

}
